/**
 * linkage
 * <select name="cityId" id="two" data-dep="one" data-url="${ctx}/hatCity/getCityByProvinceId@cityId:cityName"></select>
 * <select name="thr" id="thr" data-dep="two" data-url="${ctx}/hatArea/getAreaByCityId@areaId:areaName"></select>
 */
window.Linkage = (function () {
    var DEFAULT = {
        depAttr: 'data-dep',
        urlAttr: 'data-url',
        unselectedVal: '-1',
        unselectedText: '请选择',
        placeholderAttr: 'data-pl'
    };

    function Linkage(dom, config) {
        var me = this;

        if (!(me instanceof Linkage)) {
            return new Linkage(dom, config);
        }

        if ('string' === typeof dom) {
            dom = document.getElementById(dom);
        }

        me.dom = dom;
        me.config = config || DEFAULT;

        var dep = dom.getAttribute(me.config['depAttr']) || '';

        (dep = document.getElementById(dep)) && me._init(dep);
        me.dep = dep ? Linkage(dep, config) : null;
        !me.dep && me.load();   // 如果当前元素没有依赖, 则直接加载当前元素内容
        console.log(!me.dep);

        if (1 > dom.options.length) {
            resetOptions(dom.options, me.config['unselectedVal'], me.config['unselectedText']);
        }
    }

    Linkage.prototype = {
        _init: function (dep) {
            var me = this;

            // 当依赖对象改变时触发当前元素变化
            $(dep).change(function () {
                me.load(dep);
            });
        },
        load: function (dep) {
            var me = this,
                dom = me.dom,
                config = me.config,
                data = me.getData && me.getData() || {},
                url = dom.getAttribute(config['urlAttr']),
                pl = dom.getAttribute(config['placeholderAttr']),
                i, kv;

            if (url) {
                // 存在 url 的话, 重置为默认值
                resetOptions(dom.options, config['unselectedVal'], pl || config['unselectedText']);
                $(dom).change();

                // 有依赖但依赖选中的是无效值
                if (dep && (data[dep.name] == config['unselectedVal'] || data[dep.name] === undefined)) {
                    return;
                }

                i = url.lastIndexOf('@');
                kv = -1 < i ? url.substring(i + 1).split(':') : ['id', 'name'];
                url = -1 < i ? url.substring(0, i) : url;

                $.ajax({
                    url: url,
                    data: data,
                    dataType: 'json',
                    success: function (data) {
                        var i, opt;
                        for (i = 0; i < data.length; i++) {
                            opt = new Option(data[i][kv[1]], data[i][kv[0]]);
                            dom.appendChild(opt);
                        }
                    }
                });
            }

        },
        getDependencies: function (callback) {
            var me = this,
                dep = me.dep,
                callback = callback || function (d) {
                    return d;
                },
                deps = [];

            if (dep) {
                deps = dep.getDependencies ? dep.getDependencies(callback) : deps || deps;
                deps.push(callback.call(me, dep));
            }

            return deps;
        },
        getChains: function (callback) {
            var me = this,
                callback = callback || function (d) {
                    return d;
                },
                deps = me.getDependencies(callback);

            deps.push(callback.call(me, me));
            return deps;
        },
        getData: function () {
            var me = this,
                ret = {};
            me.getDependencies(function (dep) {
                ret[dep.dom.name] = dep.dom.value;
            });
            return ret;
        },
        getVal: function () {
            var me = this,
                val = me.getDependencies(function (dep) {
                    return dep.dom.value;
                });
            val.push(me.dom.value);
            return val;
        }
    };

    function resetOptions(opts, defVal, defText) {
        while (opts.length) {
            opts.remove(0);
        }
        if (defVal) {
            defText = undefined !== defText ? defText : defVal;
            opts.add(new Option(defText, defVal, true));
        }
    }

    return Linkage;
})();
